package com.cooper.scraper;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.cooper.android.comm.LRULinkedHashMap;
import com.cooper.android.comm.LRULinkedHashMap.RemoveEldestEntryListener;

import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class Downloader<T extends Scraper> implements
		RemoveEldestEntryListener<String, String> {

	public static final int MESSAGE_DOWNLOAD_COMPLETE = 1;
	public static final int MESSAGE_DOWNLOAD_FAIL = 2;
	public static final int MESSAGE_DOWNLOAD_CANCEL = 3;

	private static final String BITMAP_CACHE_INFO_FILE_NAME = "bitmap_cache.ser";
	protected DownloadParams params;
	private LRULinkedHashMap<String, String> bitmapCache = null;

	private DownloadStatusListener statusListener;

	private Map<String, Future<?>> requests = new HashMap<String, Future<?>>();

	private ExecutorService threadPool;

	private List<DownloadSubscribe> subscribes = new ArrayList<DownloadSubscribe>();

	protected Downloader() {
		super();
		params = new DownloadParams();
		init();
	}

	protected Downloader(DownloadParams params) {
		super();
		this.params = params;
		init();
	}

	private void init() {
		if (params.getPageBufferSize() > 0)
			params.setPageBuffer(new LRULinkedHashMap<String, String>(params
					.getPageBufferSize()));
		initBitmapBuffer();
		params.setHandler(handler);
		threadPool = Executors.newFixedThreadPool(Runtime.getRuntime()
				.availableProcessors() * params.getThreadPoolSize());
	}

	@SuppressWarnings("unchecked")
	private void initBitmapBuffer() {
		String cacheDirPath = params.getBitmapCacheDir();
		if (params.getBitmapBufferSize() > 0) {
			if (params.isSaveBitmapBufferToFile()) {
				try {
					FileInputStream istream = new FileInputStream(cacheDirPath
							+ "/" + BITMAP_CACHE_INFO_FILE_NAME);
					try {
						ObjectInputStream in = new ObjectInputStream(istream);
						int serMapSize = in.readInt();
						if (serMapSize == params.getBitmapBufferSize()) {
							bitmapCache = (LRULinkedHashMap<String, String>) in
									.readObject();
							Log.d("Downloader", "load cache frome file");
						}
					} finally {
						istream.close();
					}

				} catch (Exception e) {
					Log.w("Downloader", e.getMessage(), e);
				}
			}
			if (bitmapCache == null) {
				bitmapCache = new LRULinkedHashMap<String, String>(
						params.getBitmapBufferSize());
			}
			bitmapCache.setRemoveEldestEntryListener(this);
			params.setBitmapBuffer(bitmapCache);

			File cacheDir = new File(cacheDirPath);
			for (File cacheFile : cacheDir.listFiles()) {
				if (!cacheFile.equals(".") && !cacheFile.equals("..")
						&& cacheFile.isFile()
						&& !bitmapCache.values().contains(cacheFile.getName())) {
					cacheFile.delete();
				}
			}
		} else {
			File cacheDir = new File(cacheDirPath);
			for (File cacheFile : cacheDir.listFiles()) {
				if (!cacheFile.equals(".") && !cacheFile.equals("..")) {
					cacheFile.delete();
				}
			}
		}
	}

	public DownloadParams getParams() {
		return params;
	}

	private Handler handler = new Handler() {

		@Override
		public synchronized void handleMessage(Message msg) {
			List<DownloadSubscribe> tempSubscribes = new ArrayList<DownloadSubscribe>(
					subscribes);
			Set<String> keys;
			switch (msg.what) {
			case Downloader.MESSAGE_DOWNLOAD_COMPLETE:
				keys = params.completeKeys();
				for (String key : keys) {
					Object resultData = params.getResult(key);
					for (DownloadSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadComplete(key, resultData);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								if (statusListener != null) {
									statusListener.onSubscribeError(e);
								}
								Log.e("Downloader", e.getMessage(), e);
							}
						}
					}
				}
				break;
			case Downloader.MESSAGE_DOWNLOAD_FAIL:
				keys = params.failKeys();
				for (String key : keys) {
					Integer errorCode = params.failErrorCode(key);
					for (DownloadSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadFail(key, errorCode);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								if (statusListener != null) {
									statusListener.onSubscribeError(e);
								}
								Log.e("Downloader", e.getMessage(), e);
							}
						}
					}
				}
				break;
			case Downloader.MESSAGE_DOWNLOAD_CANCEL:
				keys = params.cancelKeys();
				for (String key : keys) {
					for (DownloadSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadCancel(key);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								if (statusListener != null) {
									statusListener.onSubscribeError(e);
								}
								Log.e("Downloader", e.getMessage(), e);
							}
						}
					}
				}
				break;
			}
		}
	};

	private boolean busy = false;

	protected void run(String key, DownloadThread thread) {
		cleanRequest();
		if (requests.get(key) != null) {
			Log.w("Downloader", "thread in request");
		} else
			requests.put(key, threadPool.submit(thread));
		if (!busy && requests.size() >= params.getThreadMaxSize()) {
			busy = true;
			statusListener.onBusy();
		}
	}

	public DownloadStatusListener getStatusListener() {
		return statusListener;
	}

	public void setStatusListener(DownloadStatusListener statusListener) {
		this.statusListener = statusListener;
	}

	public boolean subscribed(DownloadSubscribe subscribe) {
		return subscribes.contains(subscribe);
	}

	public void subscribe(DownloadSubscribe subscribe) {
		if (!subscribes.contains(subscribe)) {
			subscribes.remove(subscribe);
		}
		subscribes.add(subscribe);
	}

	public int getTaskCount(DownloadSubscribe subscribe) {
		cleanRequest();
		int result = 0;
		for (Entry<String, Future<?>> request : requests.entrySet()) {
			if (subscribe.subscribed(request.getKey())) {
				result++;
			}
		}
		return result;
	}

	public void cancelSubscribe(DownloadSubscribe subscribe) {
		cancelSubscribe(subscribe, true);
	}

	public void cancelSubscribe(DownloadSubscribe subscribe,
			boolean cancelDownload) {
		if (subscribes.contains(subscribe)) {
			if (cancelDownload) {
				cancel(subscribe);
			}
			subscribes.remove(subscribe);
		}
	}

	public void cancel(String key) {
		cleanRequest();
		Future<?> task = requests.get(key);
		if (task != null) {
			task.cancel(true);
			requests.remove(key);
			Log.d("Downloader", "cancel Download from cancel for key:" + key);
		}
	}

	public void cancel() {
		cleanRequest();
		Log.d("Downloader", "Downloader is cancel all");
		for (Entry<String, Future<?>> request : requests.entrySet()) {
			request.getValue().cancel(true);
		}
		requests.clear();
	}

	public void cancel(DownloadSubscribe subscribe) {
		cleanRequest();
		List<String> cancelRequests = new ArrayList<String>();
		for (Entry<String, Future<?>> request : requests.entrySet()) {
			if (subscribe.subscribed(request.getKey())) {
				cancelRequests.add(request.getKey());
			}
		}
		for (String key : cancelRequests) {
			requests.get(key).cancel(true);
			requests.remove(key);
			Log.d("Downloader", "cancel Download from cancel subscribe key:"
					+ key);
		}

	}

	private void cleanRequest() {
		List<String> removeKeys = new ArrayList<String>();
		for (Entry<String, Future<?>> request : requests.entrySet()) {
			if (request.getValue().isDone() || request.getValue().isCancelled()) {
				removeKeys.add(request.getKey());
			}
		}
		for (String key : removeKeys) {
			requests.remove(key);
		}
	}

	public void downloadBitmap(final String url, String key, final float width,
			final float height) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new DownloadThread(params) {
			@Override
			public void run() {
				super.run();
				try {
					Bitmap result = null;
					Bitmap srcImg = httpDownload.downloadBitmap(url);
					if (srcImg != null){
						result = Bitmap.createScaledBitmap(srcImg, (int) width,
								(int) height, true);
						if (srcImg != null && !srcImg.isRecycled()){
							srcImg.recycle();
						}
					}
					complete(useKey, result);
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	public void downloadBitmap(final String url, String key) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new DownloadThread(params) {
			@Override
			public void run() {
				super.run();
				try {
					Bitmap result = httpDownload.downloadBitmap(url);
					complete(useKey, result);
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	public void downloadFile(final String url, final File file, String key) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new DownloadThread(params) {
			@Override
			public void run() {
				super.run();
				try {
					httpDownload.downloadFile(url, file);
					complete(useKey, file.getPath());
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	public void destroy() {
		subscribes.clear();
		cancel();		
		if (bitmapCache != null && params.isSaveBitmapBufferToFile()) {
			bitmapCache.setRemoveEldestEntryListener(null);
			String cachePath = params.getBitmapCacheDir();
			for (Entry<String, String> cache : bitmapCache.entrySet()) {
				File cacheBitmapFile = new File(cachePath + "/"
						+ cache.getValue());
				if (!cacheBitmapFile.exists()) {
					bitmapCache.remove(cache.getKey());
				}
			}
			String serFilePath = cachePath + "/" + BITMAP_CACHE_INFO_FILE_NAME;
			File serFile = new File(serFilePath);
			if (serFile.exists()) {
				serFile.delete();
			}
			try {
				FileOutputStream outstream = new FileOutputStream(serFilePath);
				try {
					ObjectOutputStream out = new ObjectOutputStream(outstream);
					out.writeInt(params.getBitmapBufferSize());
					out.writeObject(bitmapCache);
					out.flush();
				} finally {
					outstream.close();
				}
			} catch (IOException e) {
				Log.e("Downloader", e.getMessage(), e);
			}
		} else {
			File cacheDir = new File(params.getBitmapCacheDir());
			for (File cacheFile : cacheDir.listFiles()) {
				if (!cacheFile.equals(".") && !cacheFile.equals("..")
						&& cacheFile.isFile()) {
					cacheFile.delete();
				}
			}
		}
	}

	@Override
	public boolean onRemove(Entry<String, String> eldest) {
		File cacheFile = new File(params.getBitmapCacheDir() + "/"
				+ eldest.getValue());
		if (cacheFile.exists()) {
			cacheFile.delete();
		}
		return true;
	}
}
